/*
 * interfaz.c
 *
 *  Created on: 22/09/2011
 *      Author: utn_so
 */

#include "ppdHandler.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/socket.h>
#include <signal.h>
#include <pthread.h>


//stListaDisco listaDiscos = NULL;
stDisco *vecDiscos[10];


int32_t ObtenerDiscoLectura()
{
	//asumimos que ya hay uno
	int32_t posicion = hayUnDiscoConectado();
	int32_t cantidad = vecDiscos[posicion]->cantidadPedidos;

	int32_t i = 1;

	for (;i < 10;i++)
	{
		if (vecDiscos[i] != NULL &&
			vecDiscos[i]->discoSincronizado == 1 &&
			vecDiscos[i]->cantidadPedidos < cantidad)
		{
			cantidad = vecDiscos[i]->cantidadPedidos ;
			//idDisco =	vecDiscos[i].id;
			posicion = i;
		}
	}

	return posicion;
}

int32_t ObtenerDiscoLecturaConOmision(int32_t posOmitir)
{
	//asumimos que ya hay uno
	int32_t posicion = hayUnDiscoConectado();
	int32_t cantidad = vecDiscos[posicion]->cantidadPedidos;

	int32_t i = 1;

	for (;i < 10;i++)
	{
		if(i != posOmitir )
		{
			if (vecDiscos[i] != NULL &&
				vecDiscos[i]->discoSincronizado == 1)
			{
				cantidad = vecDiscos[i]->cantidadPedidos ;
				//idDisco =	vecDiscos[i].id;
				posicion = i;
			}
		}
	}

	return posicion;
}

int32_t hayUnDiscoConectado()
{
	int32_t i = 0;
	for (;i < 10;i++)
	{
		if (vecDiscos[i] != NULL && vecDiscos[i]->discoSincronizado == 1)
		{
			return i;
		}
	}
	return -1;

}


int32_t cantDiscosConectados()
{
	int32_t i = 0;
	int32_t cont = 0;
	for (;i < 10;i++)
	{
		if (vecDiscos[i] != NULL && vecDiscos[i]->discoSincronizado == 1)
		{
			cont++;
		}
	}
	return cont;

}

int32_t AgregarPedido (stDisco *disco,stPedido *pedido)
{


	stNodoPedido *ptr,*nuevo;
	nuevo = malloc(sizeof(stNodoPedido));
	nuevo->info = pedido;
	nuevo->sgte=NULL;

	if(disco->listaPedidos != NULL && disco->listaPedidos->info != NULL){
			for(ptr = disco->listaPedidos; ptr->sgte; ptr=ptr->sgte );
			ptr->sgte = nuevo;
		}else{
			disco->listaPedidos=nuevo;
		}


	/////////////////////////////////////////////
	int32_t payloadlength = sizeof(stPedido);
	char *mensaje = malloc(payloadlength);

	memcpy(mensaje,pedido,sizeof(stPedido));

	char *paq = serializarNipc(pedido->tipoOperacion,mensaje,payloadlength);


	int32_t nbytes = send(disco->socket,paq,sizeof(PAQUETE_NIPC_CABECERA) + payloadlength, 0);

	if(nbytes < (sizeof(PAQUETE_NIPC_CABECERA) + payloadlength))
	{
		printf("mande menos\n");
	}
	//printf("sector [%ld] tipooperacion [%d] mando [%d]\n",pedido->sector,pedido->tipoOperacion,nbytes);


	char *logMsj = malloc(100);
	int32_t pid = getpid();
	if (pedido->tipoOperacion == ESCRIBIR)
	{
		sprintf(logMsj,"Se agrego una escritura en el disco %d",disco->id);
		logMessage("operacion a procesar",pid,0,0,logMsj,disco->hayqueloguear);
	}else if(pedido->tipoOperacion == LEER)
	{
		sprintf(logMsj,"Se agrego una lectura en el disco %d",disco->id);
		logMessage("operacion a procesar",pid,0,0,logMsj,disco->hayqueloguear);
	}



	free(logMsj);

	free(mensaje);
	free(paq);

	/////////////////////////////////////////////

	return 1;

}


int32_t AgregarLectura(stPedido *pedido, pthread_mutex_t *semaforo)
{
	pthread_mutex_lock(semaforo);
	int32_t posicion = ObtenerDiscoLectura();
	pthread_mutex_unlock(semaforo);

	pthread_mutex_lock(vecDiscos[posicion]->semaforoInterno);
	int32_t exito = AgregarPedido(vecDiscos[posicion],pedido);
	vecDiscos[posicion]->cantidadPedidos = vecDiscos[posicion]->cantidadPedidos +1;
	pthread_mutex_unlock(vecDiscos[posicion]->semaforoInterno);

	//printf("se lee del disco %d sector: %ld  tipo operacion: %d\n",vecDiscos[posicion]->id,pedido->sector,pedido->tipoOperacion);

	if(exito != 1) //fallo
	{
		printf("se desconecto un disco mientras se le intentaba asignar una redistribucion \n");
		stNodoPedido *lista = vecDiscos[posicion]->listaPedidos;
		BorrarDisco(posicion);
		//RedistribuirLecturas(lista);//TODO verrrrrrrrrr seguro errorrrrrr
		//ver posible bucle infinito si no hay discos conectados
	}

	return exito;
}

int32_t AgregarEscritura(stPedido *pedido)
{
	int32_t pid = getpid();

	int32_t i = 0;

	int32_t exito = 1;
	for (;i<10;i++)
	{
		if(vecDiscos[i])
		{
			pthread_mutex_lock(vecDiscos[i]->semaforoInterno);
			exito = AgregarPedido(vecDiscos[i],pedido);
			pthread_mutex_unlock(vecDiscos[i]->semaforoInterno);
			//if (exito != 1) TODO: ver manejo de errores proximas entregas
		}
	}


	return exito;
}

int32_t AgregarEscrituraSincronizacion(stPedido *pedido, pthread_mutex_t *semaforo)
{
	/*pthread_mutex_lock(semaforo);
	int32_t posicion = ObtenerDiscoPorSocket(pedido->socketPFS);
	pthread_mutex_unlock(semaforo);

	if(posicion > -1)
	{
		pedido->tipoOperacion=SINCROESCRIBIR;

		pthread_mutex_lock(vecDiscos[posicion]->semaforoInterno);
		posicion = AgregarPedido(vecDiscos[posicion],pedido);
		pthread_mutex_unlock(vecDiscos[posicion]->semaforoInterno);
	}
	else
	{
		//error ver SI FALLA
	}*/


	pedido->tipoOperacion=SINCROESCRIBIR;

	int32_t payloadlength = sizeof(stPedido);
	char *mensaje = malloc(payloadlength);

	memcpy(mensaje,pedido,sizeof(stPedido));

	char *paq = serializarNipc(pedido->tipoOperacion,mensaje,payloadlength);


	int32_t nbytes = send(pedido->socketPFS,paq,sizeof(PAQUETE_NIPC_CABECERA) + payloadlength, 0);

	if(nbytes < (sizeof(PAQUETE_NIPC_CABECERA) + payloadlength))
	{
		printf("mande menos SINCROESCRIBIR\n");
	}
	free(mensaje);
	free(paq);

	return 1;
}


void RedistribuirLecturas(stNodoPedido *listaPedidos, pthread_mutex_t *semaforo)
{
	stNodoPedido *aux;//,*borrar; SACO BORRAR PORQUE ES AL PEDO SIEMPRE TENGO QUE BORRAR AUX

	while(listaPedidos)
	{
		aux = listaPedidos;
		listaPedidos = listaPedidos->sgte;
		if (aux != NULL && aux->info != NULL && (aux->info->tipoOperacion == LEER || aux->info->tipoOperacion == SINCROLEER))
		{
			//return;
			AgregarLectura(aux->info,semaforo); //TODO VERRRRRRR
		}
		//borrar = sacarNodoListaPedidos(aux->info,&listaPedidos);
		//free(borrar);
		free(aux);

	}

}

void InicializarDiscos()
{
	int32_t i = 0;
	for (;i<10;i++)
	{
		vecDiscos[i] = NULL;
	}
}

int32_t ObtenerDiscoVacio()
{
	int32_t i = 0;
	for (;i<10;i++)
	{
		if (vecDiscos[i] == NULL) return i;
	}
	return i;
}

void BorrarDisco(int32_t posicion)
{
	vecDiscos[posicion]->discoSincronizado = 0;
}


int32_t AgregarDisco(PAQUETE_NIPC *paq, int32_t socket, char *dirIP, char *puerto,
		pthread_mutex_t *semaforo, int32_t hayQueLoguear)
{

	long cantSectores;

	pthread_mutex_lock(semaforo);

	if(ObtenerDiscoPorId(atoi(paq->payload))!= -1)
	{
		printf("Ya existe el ID %d \n",atoi(paq->payload));
		pthread_mutex_unlock(semaforo);
		return 0;
	}
	int32_t posicion= ObtenerDiscoVacio();

	int32_t hayUnDisco = hayUnDiscoConectado();

	pthread_mutex_unlock(semaforo);

	vecDiscos[posicion] = (pstDisco)malloc(sizeof(stDisco));
	char *respuesta ;
	if(hayUnDisco > -1)
	{
		respuesta =serializarNipcSinPayload(HANDSHAKE,0);
		vecDiscos[posicion]->discoSincronizado = 0;

		//char *paq = serializarNipc(TAMANIODISCO,"",0);
		//send(socket,paq,sizeof(PAQUETE_NIPC_CABECERA),0);
		//free(paq);
		PAQUETE_NIPC_CABECERA *cabe = (PAQUETE_NIPC_CABECERA*)malloc(sizeof(PAQUETE_NIPC_CABECERA));
		int32_t nbytes;

		/*nbytes = recv(socket,cabe,sizeof(PAQUETE_NIPC_CABECERA),MSG_WAITALL);

		if(nbytes<=0)
		{
			//error
		}
		else
		{
			char *payload = (char*)malloc(cabe->payloadlength);

			nbytes = recv(socket,payload,cabe->payloadlength,MSG_WAITALL);
			if(nbytes > 0)
			{
				 cantSectores = atol(payload);
			}
		}*/
	}
	else
	{
		respuesta =serializarNipcSinPayload(HANDSHAKE,1);
		vecDiscos[posicion]->discoSincronizado = 1;
	}

	send(socket,respuesta,sizeof(PAQUETE_NIPC_CABECERA),0);
	free(respuesta);


	vecDiscos[posicion]->cantidadPedidos = 0;
	vecDiscos[posicion]->id = atoi(paq->payload);
	vecDiscos[posicion]->socket = socket;
	vecDiscos[posicion]->hayqueloguear = hayQueLoguear;




	stNodoPedido * listaPedidos = (stNodoPedido *)malloc(sizeof(stNodoPedido));
	listaPedidos->info = NULL;
	listaPedidos->sgte = NULL;

	vecDiscos[posicion]->listaPedidos = listaPedidos;


	stDatosPPD *args = (stDatosPPD *)malloc(sizeof(stDatosPPD));
	args->disco = vecDiscos[posicion];

	args->dirIp = &dirIP;
	args->puerto = &puerto;


	vecDiscos[posicion]->semaforoInterno = malloc(sizeof(pthread_mutex_t));

	args->semaforo = semaforo;
	args->horacInicioSincro = (char*)malloc(20);

	char *fechayhora = (char*)malloc(12);
	time_t hora = time (NULL);
	struct tm *tmPtr = localtime(&hora);
	strftime( fechayhora, 10, "%H:%M:%S", tmPtr );
	strcpy(args->horacInicioSincro,fechayhora);



	pthread_mutex_init(vecDiscos[posicion]->semaforoInterno,NULL);

	pthread_t thread;
	pthread_create(&thread,NULL,(void*)&atenderPPD,args);

	//pthread_mutex_lock(semaforo);
	vecDiscos[posicion]->threadId = thread;
	//pthread_mutex_unlock(semaforo);



	if(hayUnDisco > -1)
	{
		IniciarSincronizacion(socket,semaforo,cantSectores,posicion);
	}



	return 1;

}


int32_t ObtenerDiscoPorSocket(int32_t socket)
{
	int32_t i = 0;
	for (;i<10;i++)
	{

		if((vecDiscos[i]!= NULL)&& (vecDiscos[i]->socket == socket)) return i;
	}
	return -1;
}


int32_t ObtenerDiscoPorId(int32_t id)
{
	int32_t i = 0;
	for (;i<10;i++)
	{

		if((vecDiscos[i]!= NULL)&& (vecDiscos[i]->id == id)) return i;
	}
	return -1;
}

stNodoPedido *sacarNodoListaPedidos (stPedido *pedido,stNodoPedido **listaPedidos)
{
	stNodoPedido *aux,*borrar,*anterior;


	//aux = listaPedidos;
	if ((*listaPedidos) != NULL && (*listaPedidos)->info != NULL &&
			(*listaPedidos)->info->sector == pedido->sector &&
			(*listaPedidos)->info->tipoOperacion == pedido->tipoOperacion)
	{
		//borrar = aux;
		//aux = (aux)->sgte;
		//listaPedidos = aux;
		borrar  = (*listaPedidos);
		(*listaPedidos) = (*listaPedidos)->sgte;
		return borrar;
	}

	aux = *listaPedidos;

	while(aux != NULL && aux->info!= NULL)
	{
		anterior = aux;
		aux = aux->sgte;
		if(aux != NULL && aux->info != NULL &&
			aux->info->sector == pedido->sector &&
			aux->info->tipoOperacion == pedido->tipoOperacion)
		{
			borrar = aux;
			anterior->sgte = aux->sgte;
			break;
		}
	}
	if(aux!=NULL)
		free(aux); //TODO: SI FALLA POR MEMORIA VER ACA
	return borrar;

	//free(borrar); esto se hace despues de que haya utilizado ese pedido para contestar que ya hizo lo que tenia que hacer
}


void sacarNodoListaPedidos2 (stPedido *pedido,stNodoPedido **listaPedidos)
{
	stNodoPedido *aux,*borrar,*anterior;


	//aux = listaPedidos;
	if ((*listaPedidos) != NULL && (*listaPedidos)->info != NULL &&
			(*listaPedidos)->info->sector == pedido->sector &&
			(*listaPedidos)->info->tipoOperacion == pedido->tipoOperacion)
	{
		//borrar = aux;
		//aux = (aux)->sgte;
		//listaPedidos = aux;
		borrar  = (*listaPedidos);
		(*listaPedidos) = (*listaPedidos)->sgte;
		free(borrar->info);
		free (borrar);
		return;
	}

	aux = *listaPedidos;

	while(aux != NULL && aux->info!= NULL)
	{
		anterior = aux;
		aux = aux->sgte;
		if(aux != NULL && aux->info != NULL &&
			aux->info->sector == pedido->sector &&
			aux->info->tipoOperacion == pedido->tipoOperacion)
		{
			borrar = aux;
			anterior->sgte = aux->sgte;
			break;
		}
	}
	free(borrar->info);
	free (borrar);
		return;

	//free(borrar); esto se hace despues de que haya utilizado ese pedido para contestar que ya hizo lo que tenia que hacer
}


void IniciarSincronizacion(int32_t socketPPD, pthread_mutex_t *semaforo, long cantSectores,int32_t posDiscoASincronizar)
{
	stPedido *pedido = (stPedido*)malloc(sizeof(stPedido));
	pedido->sector = 0;
	pedido->socketPFS = socketPPD;

	int32_t cantDiscos = cantDiscosConectados();

	if (cantDiscos == 1)
	{
		pedido->tipoOperacion = SINCROLEERTODOS;

		int32_t payloadlength = sizeof(stPedido);
		char *mensaje = malloc(payloadlength);

		memcpy(mensaje,pedido,sizeof(stPedido));

		char *paq = serializarNipc(pedido->tipoOperacion,mensaje,payloadlength);


		int32_t posicion = ObtenerDiscoLectura();

		int32_t nbytes = send(vecDiscos[posicion]->socket,paq,sizeof(PAQUETE_NIPC_CABECERA) + payloadlength, 0);

		if(nbytes < (sizeof(PAQUETE_NIPC_CABECERA) + payloadlength))
		{
			printf("mande menos\n");
		}


		free(mensaje);
		free(paq);
	}
	else
	{
		int32_t posicion = ObtenerDiscoLectura();
		pedido->tipoOperacion = SINCROLEERPARES;

		int32_t payloadlength = sizeof(stPedido);
		char *mensaje = malloc(payloadlength);

		memcpy(mensaje,pedido,sizeof(stPedido));

		char *paq = serializarNipc(pedido->tipoOperacion,mensaje,payloadlength);


		int32_t posicion2 =  ObtenerDiscoLecturaConOmision(posicion);

		vecDiscos[posDiscoASincronizar]->discoSincronizado =vecDiscos[posicion2]->socket;


		int32_t nbytes = send(vecDiscos[posicion]->socket,paq,sizeof(PAQUETE_NIPC_CABECERA) + payloadlength, 0);

		if(nbytes < (sizeof(PAQUETE_NIPC_CABECERA) + payloadlength))
		{
			printf("mande menos\n");
		}

		free(mensaje);
		free(paq);

	}
	free(pedido);

	//printf("mande todos \n");

}

void HabilitarDisco(int32_t socket, pthread_mutex_t *semaforo)
{
	pthread_mutex_lock(semaforo);
	int32_t posicion = ObtenerDiscoPorSocket(socket);
	pthread_mutex_unlock(semaforo);
	if (posicion>-1)
	{

		vecDiscos[posicion]->discoSincronizado = 1;

	}
}

void Redistribuir(int32_t socket, pthread_mutex_t *semaforo)
{

	pthread_mutex_lock(semaforo);
	int32_t posicion = ObtenerDiscoPorSocket(socket);
	pthread_mutex_unlock(semaforo);
	if (posicion > -1)
	{
		vecDiscos[posicion]->discoSincronizado = 0;

		pthread_mutex_lock(vecDiscos[posicion]->semaforoInterno);
		RedistribuirLecturas(vecDiscos[posicion]->listaPedidos,semaforo);
		pthread_mutex_unlock(vecDiscos[posicion]->semaforoInterno);

		int32_t pid = getpid();
		char *msj = malloc(100);
		sprintf(msj,"el disco que se desconecto fue %d",vecDiscos[posicion]->id);
		logMessage("desconexion de un disco",pid,0,0,msj,vecDiscos[posicion]->hayqueloguear);
		free(msj);

		int32_t hayquelog = vecDiscos[posicion]->hayqueloguear;

		pthread_mutex_lock(semaforo);

		if (hayUnDiscoConectado() == -1)
		{
			logMessage("Cambio de estado RAID",pid,0,0,"INACTIVO",hayquelog);
		}
		pthread_mutex_unlock(semaforo);
		//pthread_kill(vecDiscos[posicion]->threadId,SIGTERM);
	}


}


